""" 
unittest --- 单元测试框架

unittest 单元测试框架是受到 JUnit 的启发，与其他语言中的主流单元测试框架有着相似的风格。
其支持测试自动化，配置共享和关机代码测试。
支持将测试样例聚合到测试集中，并将测试与报告框架独立。

为了实现这些，unittest 通过面向对象的方式支持了一些重要的概念。

  测试脚手架
    test fixture 表示为了开展一项或多项测试所需要进行的准备工作，以及所有相关的清理操作。
    举个例子，这可能包含创建临时或代理的数据库、目录，再或者启动一个服务器进程。

  测试用例
    一个测试用例是一个独立的测试单元。
    它检查输入特定的数据时的响应。 
    unittest 提供一个基类： TestCase ，用于新建测试用例。

  测试套件
    test suite 是一系列的测试用例，或测试套件，或两者皆有。
    它用于归档需要一起执行的测试。

  测试运行器（test runner）
    test runner 是一个用于执行和输出测试结果的组件。
    这个运行器可能使用图形接口、文本接口，或返回一个特定的值表示运行测试的结果。

"""

import unittest
from contact import Contact


# 只有以test开头的方法才会被确认为是测试方法，测试时才会执行。
class TestContact(unittest.TestCase):
# 针对Contact类的测试

  def setUp(self):
    self.emptyContact = Contact({})

  def testFirstAdd(self):
    # 测试：往通讯录中添加新记录能添加成功。
    emptyContact = Contact({})
    emptyContact.add('xiaop', '1586936xxxx')
    self.assertTrue(len(emptyContact.getContacts()), 1)
    self.assertTrue(emptyContact.getContacts()['xiaop'], '1586936xxxx')

  def testPhoneLenError(self):
    # 测试：校验添加的新记录的电话号码的长度为11位
    emptyContact = Contact({})
    with self.assertRaisesRegex(ValueError, "电话号码应该等于11位。"):
      emptyContact.add('xiaop', '1586936xxxxxx')
  
  def testNameIsIn(self):
    # 测试：校验添加的新记录的name已经存在通讯录中
    emptyContact = Contact({})
    emptyContact.add('xiaop', "1586936xxxx")
    with self.assertRaisesRegexp(ValueError, "通讯录中已经存在此记录，请修改原记录。"):
      emptyContact.add('xiaop', "1586936xxxx")

unittest.main()


""" 
继承 unittest.TestCase 就创建了一个测试样例。
上述三个独立的测试是三个类的方法，这些方法的命名都以 test 开头。 
这个命名约定告诉测试运行者类的哪些方法表示测试。

每个测试的关键是：调用 assertEqual() 来检查预期的输出； 调用 assertTrue() 或 assertFalse() 来验证一个条件；
调用 assertRaises() 来验证抛出了一个特定的异常。
使用这些方法而不是 assert 语句是为了让测试运行者能聚合所有的测试结果并产生结果报告。

通过 setUp() 和 tearDown() 方法，可以设置测试开始前与完成后需要执行的指令。 
若 setUp() 成功运行，无论测试方法是否成功，都会运行 tearDown() 。
在 组织你的测试代码 中，对此有更为详细的描述。

最后的代码块中，演示了运行测试的一个简单的方法。 
unittest.main() 提供了一个测试脚本的命令行接口。
"""

""" 
以下是常用的断言方法
TestCase 类提供了一些断言方法用于检查并报告失败。 下表列出了最常用的方法（请查看下文的其他表来了解更多的断言方法）:

方法                                检查对象                                注释

assertEqual(a, b)                   a == b

assertNotEqual(a, b)                a != b

assertTrue(x)                       bool(x) is True

assertFalse(x)                      bool(x) is False

assertIs(a, b)                      a is b

assertIsNot(a, b)                   a is not b

assertIsNone(x)                     x is None

assertIsNotNone(x)                  x is not None

assertIn(a, b)                      a in b

assertNotIn(a, b)                   a not in b

assertIsInstance(a, b)              isinstance(a, b)

assertNotIsInstance(a, b)           not isinstance(a, b)


"""

""" 
with 语句上下文管理器
https://docs.python.org/zh-cn/3.8/reference/compound_stmts.html#with


with 语句用于包装带有使用上下文管理器 (参见 with 语句上下文管理器 一节) 定义的方法的代码块的执行。 
这允许对普通的 try...except...finally 使用模式进行封装以方便地重用。

with_stmt ::=  "with" with_item ("," with_item)* ":" suite
with_item ::=  expression ["as" target]

带有一个“项目”的 with 语句的执行过程如下:
  对上下文表达式 (在 with_item 中给出的表达式) 求值以获得一个上下文管理器。
  载入上下文管理器的 __enter__() 以便后续使用。
  载入上下文管理器的 __exit__() 以便后续使用。
  发起调用上下文管理器的 __enter__() 方法。
  如果 with 语句中包含一个目标，来自 __enter__() 的返回值将被赋值给它。
    注解 with 语句会保证如果 __enter__() 方法返回时未发生错误，则 __exit__() 将总是被调用。 
    因此，如果在对目标列表赋值期间发生错误，则会将其视为在语句体内部发生的错误。 
    参见下面的第 6 步。
  执行语句体。
  发起调用上下文管理器的 __exit__() 方法。 
    如果语句体的退出是由异常导致的，则其类型、值和回溯信息将被作为参数传递给 __exit__()。
    否则的话，将提供三个 None 参数。

    如果语句体的退出是由异常导致的，并且来自 __exit__() 方法的返回值为假，则该异常会被重新引发。 
    如果返回值为真，则该异常会被抑制，并会继续执行 with 语句之后的语句。

    如果语句体由于异常以外的任何原因退出，则来自 __exit__() 的返回值会被忽略，并会在该类退出正常的发生位置继续执行。
"""


